﻿<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="utf-8">
    <title>{{title}}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <style>
        :root {
            --primary-color: #2196F3;
            --hover-color: #1976D2;
            --background: #f5f5f5;
            --text-color: #333;
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', system-ui, sans-serif;
        }

        body {
            background: var(--background);
            color: var(--text-color);
            line-height: 1;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 1rem;
        }

        .header {
            text-align: center;
            padding: 2rem 0;
            padding-top: 0px;
            padding-bottom: 10px;
        }

        .header h1 {
            color: var(--primary-color);
            font-size: 1.6rem;
            margin-bottom: 0.5rem;
        }

        .controls {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
            gap: 1rem;
            padding: 0.1rem 0;
            padding-top: 0px !important;
            padding-bottom: 0px !important;
        }

        .button-group {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 0.8rem;
        }

        button {
            background: var(--primary-color);
            color: white;
            border: none;
            padding: 0.5rem 0.5rem;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s ease;
            font-size: 0.95rem;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }

        button:hover {
            background: var(--hover-color);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }

        .main-content {
            display: grid;
            grid-template-columns: 1fr;
            gap: 1.5rem;
            margin: 2rem 0;
            margin-top: 0.4rem;
            margin-bottom: 0.4rem;
        }

        .preview-area {
            position: relative;
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        }

        #div11 {
            position: relative;
            width: 100%;
            padding-top: 56.25%;
            /* 16:9 比例 */
            overflow: hidden;
            border-radius: 8px;
        }

        canvas,
        #img1 {
            position: absolute;
            top: 0;
            left: 0;
            width: 100% !important;
            height: 100% !important;
            object-fit: contain;
        }

        canvas {
            z-index: 1;
        }

        .status-bar {
            display: flex;
            justify-content: space-between;
            background: white;
            border-radius: 8px;
            padding: 1rem;
        }

        .footer {
            text-align: center;
            padding-top: 10px;
        }

        @media (max-width: 540px) {
            .button-group {
                grid-template-columns: repeat(2, 1fr);
                gap: 0.4rem;
            }

            .controls {
                grid-template-columns: 1fr;
            }

            button {
                padding: 0.7rem;
                font-size: 0.9rem;
            }
        }
    </style>
</head>

<body>
    <div class="container">
        <header class="header">
            <h1>{{title}}</h1>
            <p id="file1">当前文件：未选择</p>
        </header>

        <div class="controls">
            <div class="button-group">
                <button id="btn1">▶️ 开始播放</button>
                <button id="btn2">⏹️ 退出播放</button>
                <button id="btn3">📑 显示PPT</button>
                <button id="btn4">🖥️ 显示桌面</button>
            </div>
        </div>

        <main class="main-content">
            <div class="preview-area">
                <div id="div11">
                    <canvas id="c1"></canvas>
                    <img id="img1" src="" alt="PPT预览">
                </div>
                <div class="status-bar">
                    <span id="time1">播放时长：</span>
                    <span id="t1">当前页：</span>
                    <span id="t2">动画步骤：</span>
                </div>
            </div>
        </main>

        <div class="controls">
            <div class="button-group">
                <button id="btn5">⏮️ 第一页</button>
                <button id="btn6">⏭️ 最后一页</button>
                <button id="btn7">◀️ 上一步</button>
                <button id="btn8">▶️ 下一步</button>
            </div>
        </div>

        <footer class="footer">
            <p id="Notes1">提示：使用方向键可快速翻页</p>
            <br />
            <label id="about">囧囧囧</label>              
        </footer>

        <div id="notificationArea" class="notificationArea"></div>
        
    </div>

    <script>
        /**
         * PPT控制客户端 - 优化版
         * 功能：实现PPT远程控制、屏幕共享、触摸交互等功能
         */
        class PPTClient {
            constructor() {
                // 客户端配置
                this.config = {
                    screenIndex: -1,   // 当前屏幕索引 负数表示显示PPT
                    remoteMode: false, // 远程控制模式
                };

                // WebSocket实例
                this.ws = null;
                // 轮询定时器
                this.pollTimer = null;
                // DOM元素缓存
                this.domElements = {
                    pptImage: document.querySelector("#img1"),
                    canvas: document.querySelector("#c1"),
                    ctx: document.querySelector("#c1").getContext("2d"),
                    // 其他DOM元素...
                };

                this.initialize();
            }

            /**
             * 初始化客户端
             */
            initialize() {
                this.initWebSocket();
                this.bindEvents();
                this.startPolling(333); // 333ms轮询间隔
            }

            // ----------------- WebSocket 通信 -----------------
            /**
             * 初始化WebSocket连接
             */
            initWebSocket() {
                if (!window.WebSocket) {
                    console.warn('浏览器不支持WebSocket');
                    return;
                }

                const wsUrl = `ws://${location.hostname}:52528/PPTHelper`;
                this.ws = new WebSocket(wsUrl);

                // 事件监听
                this.ws.onopen = (e) => {
                    console.log('WebSocket连接已建立', e);
                };

                this.ws.onmessage = (e) => this.handleWsMessage(e);
                this.ws.onerror = (e) => {
                    console.error('WebSocket错误:', e);
                    this.reconnectWebSocket();
                };
                this.ws.onclose = (e) => {
                    console.log('WebSocket连接关闭', e);
                    this.reconnectWebSocket();
                };
            }

            /**
             * WebSocket消息处理
             */
            handleWsMessage(e) {
                try {
                    if (typeof e.data === 'string') {
                        const data = JSON.parse(e.data);
                        this.handleDataMessage(data);
                    } else if (e.data instanceof Blob) {
                        this.handleImageMessage(e.data);
                    }
                } catch (error) {
                    console.error('消息处理错误:', error);
                }
            }

            // ----------------- 消息处理 -----------------
            /**
             * 处理JSON格式消息
             */
            handleDataMessage(data) {
                console.log('收到数据消息:', data);
                this.updateUI(data);

                // WPP特殊处理
                if (data.IsWpp) {
                    document.querySelectorAll(".wpp-disabled").forEach(el => el.disabled = true);
                    document.querySelector("#t2").textContent = `动画步骤：-`;
                }
            }

            /**
             * 处理图片类型消息
             */
            handleImageMessage(blob) {
                console.log('收到图片消息');
                const imageUrl = URL.createObjectURL(blob);
                this.updatePPTImage(imageUrl);
            }

            // ----------------- 界面更新 -----------------
            /**
             * 更新PPT相关界面
             */
            updateUI(data) {
                const { FileName, Time, SlideIndex, SlidesCount, ClickIndex, ClickCount, NotesText } = data;

                // 更新DOM元素
                document.querySelector("#file1").textContent = FileName;
                document.querySelector("#time1").textContent = `播放时长：${Time}`;
                document.querySelector("#t1").textContent = `当前页：${SlideIndex}/${SlidesCount}`;
                document.querySelector("#t2").textContent = `动画步骤：${ClickIndex}/${ClickCount}`;
                document.querySelector("#Notes1").textContent = NotesText;

                // 更新PPT图片
                if (this.config.screenIndex < 0) {
                    this.updatePPTImage(`/PPTHelper/Slides?name=${FileName}&page=${SlideIndex}`);
                }
            }

            /**
             * 更新PPT图片显示
             */
            updatePPTImage(url) {
                this.domElements.pptImage.src = url;
                this.domElements.pptImage.onload = () => {
                    URL.revokeObjectURL(url); // 释放内存
                    this.adjustCanvasSize();
                };
            }

            /**
             * 调整画布尺寸匹配图片
             */
            adjustCanvasSize() {
                const { canvas, pptImage } = this.domElements;
                canvas.width = pptImage.width;
                canvas.height = pptImage.height;
            }

            // ----------------- 事件处理 -----------------
            /**
             * 绑定所有事件监听
             */
            bindEvents() {
                // 按钮事件绑定
                const controlMap = {
                    '#btn1': () => this.sendHttpRequest('/PPTHelper/Play'),
                    '#btn2': () => this.sendHttpRequest('/PPTHelper/Stop'),
                    '#btn3': () => this.switchViewMode(true),
                    '#btn4': () => this.switchViewMode(false),
                    '#btn5': () => this.sendHttpRequest('/PPTHelper/First'),
                    '#btn6': () => this.sendHttpRequest('/PPTHelper/Last'),
                    '#btn7': () => this.sendHttpRequest('/PPTHelper/Previous'),
                    '#btn8': () => this.sendHttpRequest('/PPTHelper/Next'),
                    '#btn8': () => this.sendHttpRequest('/PPTHelper/Next'),
                };

                Object.entries(controlMap).forEach(([selector, handler]) => {
                    document.querySelector(selector).addEventListener('click', handler);
                });

                // 画布交互事件
                this.bindCanvasEvents();
                // 禁用右键菜单
                document.oncontextmenu = (e) => e.preventDefault();
                // 启用远程控制
                document.querySelector('#about').addEventListener('click', () => {
                    if (this.config.screenIndex >= 0) {
                        this.config.remoteMode = !this.config.remoteMode;
                        this.showNotification(`remoteMode = ${this.config.remoteMode}`);
                    }
                });

            }

            /**
             * 切换显示
             */
            switchViewMode(flag) {
                if (flag) {
                    this.config.screenIndex = -1;
                    this.config.remoteMode = false;
                } else {
                    this.config.screenIndex++;
                }
            };

            /**
             * 绑定画布交互事件
             */
            bindCanvasEvents() {
                const canvas = this.domElements.canvas;

                // 鼠标事件
                canvas.addEventListener('mouseup', (e) => {
                    if (this.config.remoteMode) this.handlePointerEvent(e);
                });

                // 触摸事件
                canvas.addEventListener('touchstart', (e) => {
                    if (e.touches.length > 1) return; // 忽略多点触控                    
                    e.preventDefault();
                    this.touchstartTime = Date.now();
                });
                canvas.addEventListener('touchend', (e) => {
                    this.touchendTime = Date.now();
                    if (this.config.remoteMode) this.handleTouchEvent(e);
                });

                // 键盘导航
                document.addEventListener('keydown', (e) => {
                    if (e.key === 'ArrowLeft') this.sendHttpRequest('/PPTHelper/Previous');
                    if (e.key === 'ArrowRight') this.sendHttpRequest('/PPTHelper/Next');
                });
            }

            // ----------------- 交互处理 -----------------
            /**
             * 处理鼠标事件
             */
            handlePointerEvent(e) {
                const pos = this.getMousePos(e);
                if (e.button === 0) { // 左键
                    this.sendPointerCommand('LeftClick', pos.x, pos.y, pos.w, pos.h);
                } else if (e.button === 2) { // 右键
                    this.sendPointerCommand('RightClick', pos.x, pos.y, pos.w, pos.h);
                }
            }

            /**
             * 处理触摸事件
             */
            handleTouchEvent(e) {
                const pos = this.getMousePos(e);
                const isLongPress = (this.touchendTime - this.touchstartTime) > 800;
                this.sendPointerCommand(isLongPress ? 'RightClick' : 'LeftClick', pos.x, pos.y, pos.w, pos.h);
                this.drawTouchEffect(pos.x, pos.y);
            }

            // 获取精确的鼠标位置
            getMousePos(event) {
                // 获取 Canvas 的绝对位置信息
                const { canvas, ctx } = this.domElements;
                const rect = canvas.getBoundingClientRect();
                const pos = {
                    left: rect.left + window.pageXOffset,
                    top: rect.top + window.pageYOffset,
                    scaleX: canvas.width / rect.width,    // 水平缩放比例
                    scaleY: canvas.height / rect.height  // 垂直缩放比例
                };

                // 计算相对位置
                let x, y;
                if (event.type.startsWith('touch')) {
                    x = event.changedTouches[0].pageX;
                    y = event.changedTouches[0].pageY;
                } else {
                    x = event.pageX;
                    y = event.pageY;
                }
                // 转换为 Canvas 坐标
                return {
                    x: parseInt((x - pos.left) * pos.scaleX),
                    y: parseInt((y - pos.top) * pos.scaleY),
                    w: parseInt(rect.width),
                    h: parseInt(rect.height),
                };
            }

            /**
             * 发送指针控制命令
             */
            sendPointerCommand(command, x, y, w, h) {
                const msg = `MouseMove,${this.config.screenIndex},${x},${y},${w},${h}`;
                this.sendWsMessage(msg);
                this.sendWsMessage(command);
            }

            // ----------------- 工具方法 -----------------
            /**
             * 发送HTTP请求
             */
            async sendHttpRequest(url) {
                try {
                    const response = await fetch(url);
                    const data = await response.json();
                    console.log('HTTP响应:', data);
                } catch (error) {
                    console.error('HTTP请求失败:', error);
                }
            }

            /**
             * 发送WebSocket消息
             */
            sendWsMessage(message) {
                if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
                    console.warn('WebSocket未就绪');
                    return;
                }
                this.ws.send(message);
            }

            /**
             * WebSocket重连机制
             */
            reconnectWebSocket() {
                setTimeout(() => {
                    console.log('尝试重新连接WebSocket...');
                    this.initWebSocket();
                }, 3000);
            }

            // ----------------- 视觉效果 -----------------
            /**
             * 绘制触摸反馈效果
             */
            drawTouchEffect(x, y) {
                let radius = 6;
                const drawFrame = () => {
                    if (radius <= 0) return;

                    this.domElements.ctx.beginPath();
                    this.domElements.ctx.arc(x, y, radius, 0, Math.PI * 2);
                    this.domElements.ctx.strokeStyle = `rgba(0,0,0,${radius / 6})`;
                    this.domElements.ctx.stroke();

                    radius--;
                    requestAnimationFrame(drawFrame);
                };
                requestAnimationFrame(drawFrame);
            }

            /**
             * 显示系统通知
             */
            showNotification(message, duration = 2000) {
                const elem = document.querySelector("#notificationArea");
                elem.textContent = message;
                elem.style.opacity = '1';
                setTimeout(() => elem.style.opacity = '0', duration);
            }

            // ----------------- 生命周期管理 -----------------
            /**
             * 启动轮询
             */
            startPolling(interval) {
                this.pollTimer = setInterval(() => {
                    this.ws?.send("Data");
                    if (this.config.screenIndex >= 0) this.ws?.send(`Screen${this.config.screenIndex % 2}`);
                }, interval);
            }

            /**
             * 清理资源
             */
            cleanup() {
                clearInterval(this.pollTimer);
                this.ws?.close();
            }
        }

        // 初始化客户端
        document.addEventListener('DOMContentLoaded', () => {
            const client = new PPTClient();
            window.pptClient = client;
            // 暴露清理方法到页面卸载
            window.addEventListener('beforeunload', () => client.cleanup());
        });
    </script>

</body>


</html>